using System;
using org.xiph.speex;
using System.Collections.Generic;
using System.Threading;

namespace SharpWind.Network
{
	public class VoiceCoder : IDisposable
	{
		#region Fields

		private int sampleRate = 0;

		private int channels = 1;

		private int mode = 1;

		private int quality = 5;

		private bool stopped = false;

		private Queue<byte[]> encodeQueue = new Queue<byte[]>();

		private Queue<byte[]> decodeQueue = new Queue<byte[]>();

		private ManualResetEvent encodeReset = new ManualResetEvent( true );

		private ManualResetEvent decodeReset = new ManualResetEvent( true );

		private SpeexEncoder speexEncoder = new SpeexEncoder();

		private SpeexDecoder speexDecoder = new SpeexDecoder();

		#endregion

		#region Events

		public event VoiceEventHandler VoiceDecoded = null;

		public event VoiceEventHandler VoiceEncoded = null;

		#endregion

		public VoiceCoder( int sampleRate )
		{
			this.sampleRate = sampleRate;

			this.speexEncoder.init( this.mode, this.quality, this.sampleRate, this.channels );
			this.speexDecoder.init( this.mode, this.sampleRate, this.channels, true );

			Thread threadEncode = new Thread( new ThreadStart( VoiceEncode ) );
			threadEncode.Name = "SharpWind Voice Encode";
			threadEncode.Start();

			Thread threadDecode = new Thread( new ThreadStart( VoiceDecode ) );
			threadDecode.Name = "SharpWind Voice Decode";
			threadDecode.Start();
		}

		#region MyRegion

		public void Encode( byte[] buffer )
		{
			this.encodeQueue.Enqueue( buffer );
			this.encodeReset.Set();
		}

		public void Decode( byte[] buffer )
		{
			this.decodeQueue.Enqueue( buffer );
			this.decodeReset.Set();
		}

		private void VoiceEncode()
		{
			while( !this.stopped )
			{
				try
				{
					while( this.encodeQueue.Count > 0 )
					{
						int bytesRead = 0;
						sbyte[] stemp = ConvertByteToSbyte( this.encodeQueue.Dequeue() );
						this.speexEncoder.processData( stemp, 0, stemp.Length );
						int frameBytes = this.speexEncoder.getProcessedDataByteSize();
						sbyte[] myEncodedAudio = new sbyte[frameBytes + 1]; // new sbyte[frameBytes + 1]
						myEncodedAudio[bytesRead++] = (sbyte)frameBytes;
						bytesRead += this.speexEncoder.getProcessedData( myEncodedAudio, bytesRead );
						byte[] utemp = ConvertSbyteToByte( myEncodedAudio );
						if( VoiceEncoded != null )
						{
							VoiceEncoded( this, new VoiceEventArgs( utemp ) );
						}
					}
				}
				catch( Exception ex )
				{
					
				}

				this.encodeReset.WaitOne();
			}
		}

		private void VoiceDecode()
		{
			while( !this.stopped )
			{
				while( this.decodeQueue.Count > 0 )
				{
					try
					{
						int offset = 0;
						sbyte[] speexBytes = ConvertByteToSbyte( this.decodeQueue.Dequeue() );
						int frameSize = (int)speexBytes[offset++];
						this.speexDecoder.processData( speexBytes, offset, frameSize );
						sbyte[] myDecodedAudio = new sbyte[this.speexDecoder.getProcessedDataByteSize()];
						this.speexDecoder.getProcessedData( myDecodedAudio, 0 );
						byte[] a = ConvertSbyteToByte( myDecodedAudio );
						if( VoiceDecoded != null )
						{
							VoiceDecoded( this, new VoiceEventArgs( a ) );
						}
					}
					catch( Exception ex )
					{
						
					}
				}

				this.decodeReset.WaitOne();
			}

		}

		public static sbyte[] ConvertByteToSbyte( byte[] a )
		{
			sbyte[] ret = new sbyte[a.Length];
			for( int i = 0; i < a.Length; i++ )
			{
				ret[i] = (sbyte)a[i]; ;
			}
			return ret;
		}

		public static byte[] ConvertSbyteToByte( sbyte[] a )
		{
			byte[] ret = new byte[a.Length];
			for( int i = 0; i < a.Length; i++ )
			{
				ret[i] = (byte)a[i];
			}
			return ret;
		}

		#endregion

		#region IDisposable Members

		public void Dispose()
		{
			this.stopped = true;
			this.encodeReset.Set();
			this.decodeReset.Set();
			this.encodeQueue.Clear();
			this.decodeQueue.Clear();
		}

		#endregion
	}
}
